<?php

class User extends Base
{
    //用户每个操作之间的最小间隔秒数
    const MIN_ACTION_INTERVAL_SECONDS = 300;
    //每个用户最小的代理切换间隔秒数
    const PROXY_MIN_INTERVAL_SECONDS = 60 * 60;
    //用户每天最多可以切换代理的次数
    const PROXY_MAX_CHANGE_LIMITS = 1;
    //用户每天最多可以执行多少次任务数据
    const USER_MAX_ACTION_LIMITS = 20;

    const TASK_TYPE = ["like", "collect", "follow", "comment", "exposure"];
    public function __construct($webapp)
    {
        parent::__construct($webapp);
        $this->customerInit();
    }


    //筛选出所有满足条件的用户
    private function customerInit(bool $force = false)
    {
        $day = date('Ymd');
        if (intval($this->rds->exists("initTaskUsers:{$day}")) == 0 || $force) {
            $command = [
                "SELECT a.id,a.type,a.create_at,a.group_id,a.proxy_type,a.city,a.cookies,a.fingerprint,a.proxy_id, b.* 
                FROM `user` a 
                LEFT JOIN (SELECT c.user_id,c.excute_info FROM user_excute c WHERE c.`day`= ?s  AND c.proxy_change_times < ?s) b 
                ON a.id = b.`user_id` WHERE a.`deleted_at` IS NULL 
                AND a.`status` = 'normal'
                AND a.create_at < ?s",
                date("Y-m-d"),
                self::PROXY_MAX_CHANGE_LIMITS,
                date("Y-m-d H:i:s", strtotime("-1 day"))
            ];

            foreach (($this->mysql)(...$command) as $user) {
                $user['create_at'] = strtotime($user['create_at'] ?? "2010-06-06");
                $this->initUser($user);
            }
            $this->rds->set("initTaskUsers:{$day}", 1);
        }
    }

    public function getUserNoTask(int $userId = 0): array
    {
        if ($userId <= 0) return [];

        // 缓存未命中，从数据库查询
        $command = [
            "SELECT u.id,u.`name`,u.cookies,p.ip,p.port,p.user,p.password FROM `user`  u LEFT JOIN `proxy` p ON u.proxy_id = p.id WHERE u.id = ?i",
            $userId
        ];

        $user = ($this->mysql)(...$command)->array();

        return $user;
    }

    private function getById(int $userId = 0): array
    {
        if ($userId <= 0) return [];

        // 优先从缓存中获取用户信息
        $user = $this->rds->hgetall("tuser:{$userId}");
        if (!empty($user)) {
            return $user;
        }

        // 缓存未命中，从数据库查询
        $command = [
            "SELECT a.id,a.type,a.create_at,a.group_id,a.proxy_type,a.city,a.proxy_id,a.cookies,a.fingerprint, b.excute_info 
                FROM `user` a 
                LEFT JOIN (SELECT c.user_id,c.excute_info FROM user_excute c WHERE c.`day`= ?s AND c.proxy_change_times < ?s) b 
                ON a.id = b.`user_id` 
                WHERE a.`deleted_at` IS NULL 
                AND a.id = ?s
                AND a.`status` = 'normal'
                AND a.create_at < ?s
                LIMIT 1",
            date('Y-m-d'),
            self::PROXY_MAX_CHANGE_LIMITS,
            $userId,
            //登录24个小时后 可以使用
            date("Y-m-d H:i:s", strtotime("-1 day"))
        ];

        $user = ($this->mysql)(...$command)->array();
        if (!empty($user)) {
            $user['create_at'] = strtotime($user['create_at'] ?? "2010-06-06");
            $this->rds->hset("tuser:{$userId}", $user);
            $this->rds->expire("tuser:{$userId}", 60 * 60);
            $this->initUser($user);
        }

        return $user;
    }

    private function initUser(array $user)
    {
        if (empty($user)) return;
        $day = date("Ymd");

        //优先读取缓存里面的执行情况
        $infoTimes = $this->rds->hmget("userExcute:{$day}:{$user['id']}", ["excute_info", "proxy_change_times"]);

        //每天最多可以切换代理的次数(针对还没有同步到数据库的情况)
        if (intval($infoTimes['proxy_change_times'] ?? 0) > self::PROXY_MAX_CHANGE_LIMITS) {
            return;
        }
        if (empty($excuteInfo = trim($infoTimes['excute_info'] ?? ""))) {
            //正常情况下数据库应该也没有excute_info信息
            //todo 异常情况可以考虑，先同步excute_info到数据库，再同步到redis
            $excuteInfo = $user['excute_info'] ?? "";
        }

        $excuteInfo = (array)json_decode($excuteInfo, true);

        //超过每日限制的账号不在接受任务分配
        $totalActionCount = 0;
        $lastActionExcuteTime  = 0;
        foreach ($excuteInfo as $type => $info) {
            $totalActionCount += intval($info['times'] ?? 0);
            $lastActionExcuteTime = max($lastActionExcuteTime, $info['excute_at'] ?? 0);
        }
        if ($totalActionCount >= self::USER_MAX_ACTION_LIMITS) {
            return;
        }



        $this->rds->zadd(
            sprintf("%s:%s:%d", $user['type'],  $day, intval($user['group_id'])),
            $lastActionExcuteTime > 0 ? $lastActionExcuteTime : $user['create_at'],
            $user['id']
        );
        //按省份分组
        if (!empty($user['city'] ?? "")) {
            $this->rds->zadd(
                sprintf("%s:%s:%d:%s", $user['type'],  $day, intval($user['group_id']), intval($user['proxy_type']) == 1 ? substr($user['city'], 0, 3) : $user['city']),
                $lastActionExcuteTime > 0 ? $lastActionExcuteTime : $user['create_at'],
                $user['id']
            );
        }
    }



    public function get(string $type = 'redbook', string $link = '', string $taskType, int $groupId = 0, string $city = '', int $forceUid = 0): array
    {
        $rs = [
            'user' => [],
            'info' => "未获取到可用账号"
        ];
        if (!in_array($taskType, self::TASK_TYPE)) {
            $rs['info'] = "任务类型错误";
            return $rs;
        }
        if (!in_array($type, ['redbook', 'douyin'])) {
            $rs['info'] = "平台类型错误";
            return $rs;
        }

        if (empty($link)) {
            $rs['info'] = "任务链接不能为空";
            return $rs;
        }

        // 标记该方法正在执行，防止同一个用户分到多个任务
        $lockKey = "taskLock:{$type}:{$groupId}:{$city}";
        if (intval($this->rds->get($lockKey)) == 1) {
            $rs['info'] = "用户分配中，请稍后再试";
            return $rs;
        } else {
            $this->rds->set($lockKey, 1);
            $this->rds->expire($lockKey, 10);
        }

        $md5Link = md5($link);
        $day = date("Ymd");
        $redisKey = empty($city)
            ? sprintf("%s:%s:%d", $type, $day, intval($groupId))
            : sprintf("%s:%s:%d:%s", $type, $day, intval($groupId), $city);

        if ($forceUid > 0) {
            $userId = $forceUid;
            $lastExcuteTime = 0;
        } else {
            $uidScore = $this->rds->zrange($redisKey, 0, 0, true);
            $userId = $uidScore ? array_key_first($uidScore) : 0;
            $lastExcuteTime = $uidScore[$userId] ?? 0;
        }


        if (in_array($taskType, ['like', 'collect', 'follow']) && $this->rds->sismember($linkKey = "linkUsers:{$md5Link}:{$taskType}", $userId)) {
            $noExcute = false;
            // 直接一次性取50个用一个
            $users = $this->rds->zrange($redisKey, 0, 99, true);
            if ($users) {
                foreach ($users as $uid => $score) {
                    if ($this->rds->sismember($linkKey, $uid) == false) {
                        $userId = $uid;
                        $lastExcuteTime = $score;
                        $noExcute = true;
                        break;
                    }
                }
            }

            if ($noExcute == false) {
                $rs['info'] = "{$type}:分组{$groupId}：{$taskType}，url:{$link},当前队列前100人都执行过该任务，待重新分配 {$linkKey},{$userId}";
                $this->rds->delete($lockKey); // 释放锁
                return $rs;
            }
        }

        $excuteKey = "userExcute:{$day}:{$userId}";
        if ($this->currentTime - $lastExcuteTime < self::MIN_ACTION_INTERVAL_SECONDS) {
            $rs['info'] = "{$userId}，距离上次执行时间间隔太短";
            if (!empty($msg = trim($this->rds->hget($excuteKey, 'msg')))) {
                $rs['info'] = $msg;
            }
            $this->rds->delete($lockKey); // 释放锁
            return $rs;
        }

        $user = $this->getById($userId);
        if (empty($user)) {
            $this->rds->zrem($redisKey, $userId);
            $rs['info'] = "用户ID：{$userId}，可能达到今日执行上线，或异常或已经被删除";

            if ($userId == 0) {
                $rs['info'] = "{$type}:分组{$groupId}：{$city},今日已经没有用户可以执行任务了";
                // 用户可能转移回来了或者其他方式恢复了，重新初始化用户
                $this->customerInit(true);
            }
            $this->rds->delete($lockKey); // 释放锁
            return $rs;
        }

        if ($user) {
            if (intval($user['group_id']) != intval($groupId)) {
                $this->rds->zrem($redisKey, $userId);
                $this->rds->delete("tuser:{$userId}");
                $rs['info'] = "{$userId}，已从：{$groupId} 转移到分组：{$user['group_id']}";
                $this->rds->delete($lockKey); // 释放锁
                return $rs;
            }

            // 获取用户的执行情况
            if ($excuteInfo = $this->rds->hgetall($excuteKey)) {
                // 判断执行的总次数是否超标 判断是否需要切换代理
                if ($this->currentTime - intval($excuteInfo['last_excute_at']) < self::MIN_ACTION_INTERVAL_SECONDS) {
                    $rs['info'] = "{$userId}，距离上次执行时间间隔太短[1]";
                    $this->rds->delete($lockKey); // 释放锁
                    return $rs;
                }
                $excuteInfo['excute_info'] = json_decode($excuteInfo['excute_info'], true);

                $totalActionCount = 0;
                foreach ($excuteInfo['excute_info'] as $type => $info) {
                    $totalActionCount += intval($info['times'] ?? 0);
                }
                if ($totalActionCount >= self::USER_MAX_ACTION_LIMITS) {
                    $this->rds->zrem($redisKey, $userId);
                    $this->rds->delete("tuser:{$userId}");
                    $rs['info'] = "{$userId}，今日执行次数已达到上限";
                    $this->rds->delete($lockKey); // 释放锁
                    return $rs;
                }

                // 判断代理是否过期（预留3分钟前端执行时间）
                if (($excuteInfo['proxy_expire_at'] ?? 0) - $this->currentTime > 180) {
                    $excuteInfo['excute_info'][$taskType]['excute_at'] = $this->currentTime;
                    $excuteInfo['excute_info'][$taskType]['times'] = intval($excuteInfo['excute_info'][$taskType]['times'] ?? 0) + 1;
                    $excuteInfo['last_excute_at'] = $this->currentTime;

                    $excuteInfo['excute_info'] = json_encode($excuteInfo['excute_info']);
                    $this->rds->hset($excuteKey, $excuteInfo);
                    $this->rds->zadd($redisKey, $this->currentTime, $userId);

                    $user['proxy'] = (array)json_decode($excuteInfo['proxy'] ?? "", true);
                    $rs['user'] = $user;
                    $rs['info'] = "success：{$userId}，代理未过期";

                    // 类型不是曝光和评论类型，加入链接执行用户set
                    if ($taskType != 'exposure' && $taskType != 'comment') $this->rds->sadd($linkKey, $userId);
                    $this->rds->delete($lockKey); // 释放锁
                    return $rs;
                }

                // 分配的用户距离上次切换代理时间间隔太短，往后延迟到可以更换代理的时候再接受任务
                if (($proxyChangeIntervalSeconds = $this->currentTime - ($excuteInfo['proxy_change_at'] ?? 0)) < self::PROXY_MIN_INTERVAL_SECONDS) {
                    $this->rds->zadd(
                        $redisKey,
                        $proxyChangeAt = $this->currentTime + self::PROXY_MIN_INTERVAL_SECONDS - $proxyChangeIntervalSeconds,
                        $userId
                    );

                    // 标记代理原因导致该用户不可接收任务
                    $this->rds->hset($excuteKey, "msg", "{$userId}，" . date("Y-m-d H:i:s", $proxyChangeAt) . " 后可切换代理");
                    $rs['info'] = "{$userId}，距离上次切换代理时间间隔太短";
                    $this->rds->delete($lockKey); // 释放锁
                    return $rs;
                } else {
                    // 判断是否达到每天切换代理次数的限制
                    // if ($excuteInfo['proxy_change_times'] >= self::PROXY_MAX_CHANGE_LIMITS) {
                    //     $this->rds->zrem($redisKey, $userId);
                    //     $this->rds->delete("tuser:{$userId}");
                    //     $rs['info'] = "{$userId}，今日切换代理次数已达到上限";
                    //     $this->rds->delete($lockKey); // 释放锁
                    //     return $rs;
                    // }

                    $proxy = $this->webapp->mysql->proxy("WHERE id = ?i", intval($user['proxy_id'] ?? 0))->array();

                    // 已经可以切换代理
                    if ($proxy) {
                        // if ($proxy = $this->webapp->proxy->get($user['proxy_type'] ?? 0, $user['city'] ?? "")) {
                        // $this->mysql->user("WHERE id = {$userId}")->update([
                        //     'city' => $proxy['city'],
                        //     'proxy_id' => $proxy['id'],
                        // ]);
                        $excuteInfo['proxy'] = json_encode($execProxy = [
                            'id' => $proxy['id'],
                            'ip' => $proxy['ip'],
                            'port' => $proxy['port'],
                            'type' => $proxy['type'],
                            'password' => $proxy['password'] ?? "",
                            'user' => $proxy['user'] ?? "",
                        ]);
                        $excuteInfo['proxy_change_at'] = strtotime($proxy['update_at']);
                        $excuteInfo['proxy_expire_at'] = strtotime($proxy['expire_at']);
                        $excuteInfo['proxy_change_times'] = intval($excuteInfo['proxy_change_times'] ?? 0) + 1;
                        $excuteInfo['excute_info'][$taskType]['excute_at'] = $this->currentTime;
                        $excuteInfo['excute_info'][$taskType]['times'] = intval($excuteInfo['excute_info'][$taskType]['times'] ?? 0) + 1;
                        $excuteInfo['excute_info'] = json_encode($excuteInfo['excute_info']);
                        $excuteInfo['last_excute_at'] = $this->currentTime;

                        $this->rds->hset($excuteKey, $excuteInfo);
                        $this->rds->zadd($redisKey, $this->currentTime, $userId);

                        $user['proxy'] = $execProxy;
                        $rs['user'] = $user;
                        $rs['info'] = "success：{$userId}，切换代理执行任务";

                        // 类型不是曝光和评论类型，加入链接执行用户set
                        if ($taskType != 'exposure' && $taskType != 'comment') $this->rds->sadd($linkKey, $userId);
                    } else {
                        $rs['info'] = "没有可用代理：userId:{$userId},city:{$user['city']},proxy_type:{$user['proxy_type']},proxy_id:{$user['proxy_id']}";

                        $this->rds->zrem($redisKey, $userId);
                    }
                    $this->rds->delete($lockKey); // 释放锁
                    return $rs;
                }
            } else {
                $proxy = $this->webapp->mysql->proxy("WHERE id = ?i", $proxyId = intval($user['proxy_id'] ?? 0))->array();
                if ($proxyId == 0) {
                    $proxy = $this->webapp->proxy->get($user['proxy_type'] ?? 0, $user['city'] ?? "");
                }
                //今天还没有执行任何任务，直接分配
                if ($proxy) {
                    // if ($proxy = $this->webapp->proxy->get($user['proxy_type'] ?? 0, $user['city'] ?? "")) {
                    //更新用户代理信息
                    $this->mysql->user("WHERE id = {$userId}")->update([
                        'city' => $proxy['city'],
                        'proxy_id' => $proxy['id'],
                    ]);
                    $excuteInfo = [
                        'user_id' => $userId,
                        'day' => $day,
                        'excute_info' => json_encode([
                            $taskType => [
                                'excute_at' => $this->currentTime,
                                'times' => 1,
                            ]
                        ]),

                        'proxy' => json_encode($execProxy = [
                            'id' => $proxy['id'],
                            'ip' => $proxy['ip'],
                            'port' => $proxy['port'],
                            'type' => $proxy['type'],
                            'password' => $proxy['password'] ?? "",
                            'user' => $proxy['user'] ?? "",
                        ]),

                        'proxy_change_at' => strtotime($proxy['update_at']),
                        'proxy_change_times' => 1,
                        'proxy_expire_at' => strtotime($proxy['expire_at']),
                        'last_excute_at' => $this->currentTime,
                    ];
                    $this->rds->hset("userExcute:{$day}:{$userId}", $excuteInfo);
                    $this->rds->zadd($redisKey, $this->currentTime, $userId);
                    $user['proxy'] = $execProxy;
                    $rs['user'] = $user;
                    $rs['info'] = "success：{$userId}，今日首次执行任务";

                    //类型不是曝光和评论类型，加入链接执行用户set
                    if ($taskType != 'exposure' && $taskType != 'comment') $this->rds->sadd($linkKey, $userId);
                } else {
                    $rs['info'] = "没有可用代理first：userId:{$userId},city:{$user['city']},proxy_type:{$user['proxy_type']}";
                    $this->rds->zrem($redisKey, $userId);
                }
            }
        }
        $this->rds->delete($lockKey); // 释放锁        
        return $rs;
    }


    public function update(int $userId = 0, array $data = [])
    {
        if ($userId <= 0 || empty($data)) return false;
        return $this->mysql->user("WHERE id = ?i", $userId)->update($data);
    }


    public function login(array $form = []): int
    {
        if (
            empty($form['uid'] ?? "")
            || empty($form['cookies'] ?? "")
            || empty($form['name'] ?? "")
            || empty($form['avatar'] ?? "")
        ) {
            return 0;
        }

        $user = $this->mysql->user("WHERE type = 'redbook' AND uid = ?i", intval($form['uid'] ?? 0))->array();
        if ($user) {
            $user['cookies'] = $form['cookies'];
            $user['name'] = $form['name'];
            $user['header'] = $form['avatar'];
            $user['status'] = 'normal';
            $user['update_at'] = date(("Y-m-d H:i:s"));
            $this->mysql->user("WHERE id = ?i", $user['id'])->update($user);

            return $user['id'];
        }

        $user = [
            'type' => 'redbook',
            'name' => $form['name'] ?? '',
            'header' => $form['avatar'] ?? '',
            'cookies' => $form['cookies'] ?? '',
            'uid' => intval($form['uid'] ?? 0),
            'fingerprint' => '',
            'status' => 'normal',
            'group_id' => 0,
            'create_at' => date(("Y-m-d H:i:s")),
            'update_at' => date(("Y-m-d H:i:s")),
        ];
        $proxy = $this->webapp->proxy->get(2);
        if ($proxy) {
            $user['proxy_id'] = $proxy['id'];
            $user['city'] = $proxy['city'];
            $user['proxy_type'] = $proxy['type'];
        }


        if ($this->mysql->user->insert($user)) {
            return $this->mysql->insert_id;
        }
        return 0;
    }

    function updateCity()
    {
        $citys = require "littleBearCitys.php";
        $citys = array_keys($citys);

        foreach ($this->mysql->user("WHERE city is NULL LIMIT 100") as $user) {
            $this->mysql->user("WHERE id = {$user['id']}")->update([
                'city' => $citys[$user['id'] % count($citys)],
            ]);
        }
    }

    function updateFingerPrint()
    {
        // $this->mysql->user("WHERE fingerprint is NULL")->update([
        //     'fingerprint' => $this->createRandomFingerprint(),            
        // ]);
        foreach ($this->mysql->user("WHERE fingerprint is NULL LIMIT 100") as $user) {
            $this->mysql->user("WHERE id = {$user['id']}")->update([
                'fingerprint' => $this->createRandomFingerprint(),
            ]);
        }
    }


    /**
     * 从 Redis 中读取所有 userExcute:YYYYMMDD:* 的 Hash 数据，插入到数据库表，并在成功后删除 Redis 数据
     *
     * @param string $date 日期，格式为 YYYYMMDD（例如 20250914）
     * @return array 返回插入成功的记录数及删除的 Key 数量
     */
    public function saveExecuteInfo(string $date = "")
    {
        if (empty($date))  $date = date("Ymd", strtotime("-1 day"));

        $pattern = "userExcute:{$date}:*";
        $iterator = null;
        $keys = $this->webapp->rds->keys($pattern);

        $insertCount = $updateCount = 0;

        // 4. 遍历每个 Key 并处理数据
        foreach ($keys as $key) {
            $hashData = $this->webapp->rds->hGetAll($key);
            if (!empty($hashData)) {
                if ($this->webapp->mysql->user_excute("WHERE user_id = ?i AND day = ?s", $hashData['user_id'], $date)->array()) {
                    if ($this->webapp->mysql->user_excute("WHERE user_id = ?i AND day = ?s", $hashData['user_id'], $date)->update([
                        'excute_info' => json_encode($hashData['excute_info']),
                        'proxy' => json_encode($hashData['proxy']),
                        'proxy_change_at' => date("Y-m-d H:i:s", $hashData['proxy_change_at']),
                        'proxy_change_times' => intval($hashData['proxy_change_times']),
                        'proxy_expire_at' => date("Y-m-d H:i:s", $hashData['proxy_expire_at']),
                        'last_excute_at' => date("Y-m-d H:i:s", $hashData['last_excute_at']),
                    ])) {
                        $updateCount++;
                    }
                } else {
                    // 插入数据库
                    if ($this->webapp->mysql->user_excute->insert([
                        'user_id' => $hashData['user_id'],
                        'day' => $date,
                        'excute_info' => json_encode($hashData['excute_info']),
                        'proxy' => json_encode($hashData['proxy']),
                        'proxy_change_at' => date("Y-m-d H:i:s", $hashData['proxy_change_at']),
                        'proxy_change_times' => intval($hashData['proxy_change_times']),
                        'proxy_expire_at' => date("Y-m-d H:i:s", $hashData['proxy_expire_at']),
                        'last_excute_at' => date("Y-m-d H:i:s", $hashData['last_excute_at']),
                    ])) {
                        if ($date != date("Ymd")) {
                            $this->webapp->rds->del($key);
                        }
                        $insertCount++;
                    }
                }
            }
        }
        return ['insert' => $insertCount, 'update' => $updateCount];
    }




    // browserFingerPrint 对象
    // {
    //     "coreProduct": "chrome", // 内核，chrome | firefox，默认chrome，需要火狐内核时，填firefox
    //     "coreVersion": "130", // chrome 内核默认 130，firefox内核默认 128，所有内核版本，参考客户端界面内可选值
    //     "ostype": "PC", // 操作系统平台 PC | Android | IOS
    //     "os": "Win32", // navigator.platform值，严格与操作系统一一对应, Windows => Win32, macOS => MacIntel, Linux => Linux x86_64, iOS => iPhone, Android => Linux armv81
    //     "osVersion": "", // 操作系统版本，不填时，按照os随机，填了以后，按照所填的值范围内随机，windows候选项 11,10，Android候选项14,13,12,11,10,9，iOS候选 17.0,16.6,16.5,16.4,16.3,16.2,16.1,16.0,15.7,15.6,15.5,15.4,15.3,15.2,15.1,15.0，可填多个值，逗号分隔，比如windows: '11,10'
    //     "version": "", //浏览器版本，不填则随机，建议与coreVersion版本保持一致
    //     //   以下指纹如无特殊需求，不建议修改，只传入上方几个指纹字段即可
    //     "userAgent": "", // ua，不填则自动生成
    //     "isIpCreateTimeZone": true, // 基于IP生成对应的时区
    //     "timeZone": "", // 时区，isIpCreateTimeZone 为false时，参考附录中的时区列表
    //     "timeZoneOffset": 0, // isIpCreateTimeZone 为false时设置，时区偏移量
    //     "webRTC": "3", //webrtc 0 => 替换, 1 => 允许, 2 => 禁止, 3 => 隐私
    //     "ignoreHttpsErrors": false, // 忽略https证书错误，true, false
    //     "position": "1", //地理位置 0 => 询问, 1 => 允许, 2 => 禁止
    //     "isIpCreatePosition": true, // 是否基于IP生成对应的地理位置
    //     "lat": "", // 纬度 isIpCreatePosition 为false时设置
    //     "lng": "", // 经度 isIpCreatePosition 为false时设置
    //     "precisionData": "", //精度米 isIpCreatePosition 为false时设置
    //     "isIpCreateLanguage": true, // 是否基于IP生成对应国家的浏览器语言
    //     "languages": "", // isIpCreateLanguage 为false时设置，值参考附录
    //     "isIpCreateDisplayLanguage": false, // 是否基于IP生成对应国家的浏览器界面语言
    //     "displayLanguages": "", // isIpCreateDisplayLanguage 为false时设置，默认为空，即跟随系统，值参考附录
    //     "openWidth": 1280, // 窗口宽度，只是设置窗口打开时的尺寸，与指纹无关
    //     "openHeight": 720, // 窗口高度，只是设置窗口打开时的尺寸，与指纹无关
    //     "resolutionType": "0", // 分辨率类型 0 => 跟随电脑, 1 => 自定义，默认建议跟随电脑
    //     "resolution": "1920 x 1080", // 自定义分辨率时，具体值
    //     "windowSizeLimit": true, // 分辨率类型为自定义，且ostype为PC时，此项有效，约束窗口最大尺寸不超过分辨率
    //     "devicePixelRatio": 1, // 显示缩放比例，默认1，填写时，建议 1, 1.5, 2, 2.5, 3
    //     "fontType": "2", // 字体生成类型 0 => 系统默认 | 2 => 随机
    //     "canvas": "0", //canvas 0随机｜1关闭
    //     "webGL": "0", //webGL图像，0随机｜1关闭
    //     "webGLMeta": "0", //webgl元数据 0自定义｜1关闭
    //     "webGLManufacturer": "", // webGLMeta 自定义时，webGL厂商值，建议留空会自动生成
    //     "webGLRender": "", // webGLMeta自定义时，webGL渲染值，建议留空自动生成
    //     "audioContext": "0", // audioContext值，0随机｜1关闭
    //     "mediaDevice": "0",  // 媒体设备，0 随机 | 1 关闭
    //     "speechVoices": "0", // Speech Voices，0随机｜1关闭
    //     "hardwareConcurrency": "4", // 硬件并发数
    //     "deviceMemory": "8", // 设备内存，4，8，不要传入大于8的值
    //     "doNotTrack": "1", // doNotTrack 1开启｜0关闭
    //     "clientRectNoiseEnabled": true, // ClientRects true使用相匹配的值代替您真实的ClientRects | false每个浏览器使用当前电脑默认的ClientRects
    //     "portScanProtect": "0", // 端口扫描保护 0开启｜1关闭，注意默认开启保护，组织所有本地127的ws链接，比如某些打印机之类的，如有连接本地服务需求，建议关闭，或者在 portWhiteList 中，填写对应端口，加入白名单
    //     "portWhiteList": "", // 端口扫描保护开启时的白名单，逗号分隔
    //     "deviceInfoEnabled": true, // 自定义设备信息，默认开启
    //     "computerName": "", // deviceInfoEnabled 为true时设置，建议留空系统自动生成即可
    //     "macAddr": "", // deviceInfoEnabled 为true时设置，建议留空系统自动生成即可
    //     "disableSslCipherSuitesFlag": false, // ssl是否禁用特性，默认不禁用，注意开启后自定义设置时，有可能会导致某些网站无法访问
    //     "disableSslCipherSuites": null, // ssl 禁用特性，序列化的ssl特性值，参考附录
    //     "enablePlugins": false, // 是否启用插件指纹
    // }
    //生成指纹码
    public function createRandomFingerprint()
    {
        $winProfile = [
            'ostype' => 'PC',
            'os' => 'Win32',
            'osVersions' => ['10', '11'],
            'userAgents' => [
                'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36',
                'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.0.0 Safari/537.36',
            ],
            'resolutions' => ['1920 x 1080', '1600 x 900', '1440 x 900', '1366 x 768', '1280 x 720'],
        ];
        $macProfile = [
            'ostype' => 'PC',
            'os' => 'MacIntel',
            'osVersions' => ['12', '13', '14'],
            'userAgents' => [
                'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36',
                'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.0.0 Safari/537.36',
            ],
            'resolutions' => ['2560 x 1600', '2048 x 1536', '1920 x 1200', '1920 x 1080', '1440 x 900'],
        ];

        $osProfiles = [
            $winProfile,
            $macProfile,
        ];

        $profile = $osProfiles[array_rand($osProfiles)];
        $profile['userAgents'] = [
            'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/133.0.6943.54 Safari/537.36',
            'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/133.0.6943.142 Safari/537.36',
            'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.6998.89 Safari/537.36',
            'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/133.0.6943.142 Safari/537.36',
            'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/133.0.6943.54 Safari/537.36',
            'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.6998.178 Safari/537.36',
            'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.6998.166 Safari/537.36',
            'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/133.0.6943.127 Safari/537.36',
            'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/133.0.6943.99 Safari/537.36',
            'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.6998.166 Safari/537.36',
            'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.6998.118 Safari/537.36',
            'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.6998.166 Safari/537.36',
            'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/133.0.6943.142 Safari/537.36',
            'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/133.0.6943.54 Safari/537.36',
            'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.6998.89 Safari/537.36',
            'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.6998.118 Safari/537.36',
            'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.6998.178 Safari/537.36',
            'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/133.0.6943.99 Safari/537.36',
            'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.6998.89 Safari/537.36',
            'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/133.0.6943.127 Safari/537.36',
            'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.6998.89 Safari/537.36',
            'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/133.0.6943.99 Safari/537.36',
            'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/133.0.6943.99 Safari/537.36',
            'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/133.0.6943.127 Safari/537.36',
            'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/133.0.6943.127 Safari/537.36',
            'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/133.0.6943.99 Safari/537.36',
            'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/133.0.6943.54 Safari/537.36',
            'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/133.0.6943.54 Safari/537.36',
        ];

        $randomUserAgent = $profile['userAgents'][array_rand($profile['userAgents'])];

        // Extract Chrome version from UA
        preg_match('/(Chrome|CriOS)\\/(\\d+)/', $randomUserAgent, $matches);
        $coreVersion = $matches[2] ?? '124'; // Default if not found

        preg_match('/(Chrome|CriOS)\\/([\\d\\.]+)/', $randomUserAgent, $versionMatches);
        $version = $versionMatches[2] ?? '';


        $osVersion = '';
        if (!empty($profile['osVersions'])) {
            $osVersion = $profile['osVersions'][array_rand($profile['osVersions'])];
        }

        $resolution = $profile['resolutions'][array_rand($profile['resolutions'])];

        $devicePixelRatios = [1, 1.5, 2, 2.5, 3];
        $hardwareConcurencies = [2, 4, 8, 16];
        $deviceMemories = [4, 8];

        $fingerprint = [
            "coreProduct" => "chrome",
            "coreVersion" => $coreVersion,
            "ostype" => $profile['ostype'],
            "os" => $profile['os'],
            "osVersion" => $osVersion,
            "version" => $version,
            "userAgent" => $randomUserAgent,
            "isIpCreateTimeZone" => true,
            "webRTC" => "3",
            "position" => "1",
            "isIpCreatePosition" => true,
            "isIpCreateLanguage" => true,
            "openWidth" => 1280,
            "openHeight" => 720,
            "resolutionType" => "0",
            "resolution" => $resolution,
            "devicePixelRatio" => $devicePixelRatios[array_rand($devicePixelRatios)],
            "fontType" => "2",
            "canvas" => "0",
            "webGL" => "0",
            "webGLMeta" => "0",
            "audioContext" => "0",
            "mediaDevice" => "0",
            "speechVoices" => "0",
            "hardwareConcurrency" => $hardwareConcurencies[array_rand($hardwareConcurencies)],
            "deviceMemory" => $deviceMemories[array_rand($deviceMemories)],
            "doNotTrack" => "1",
            "clientRectNoiseEnabled" => true,
            "portScanProtect" => "0",
        ];

        list($w, $h) = explode(' x ', $resolution);
        $fingerprint['openWidth'] = rand(1024, intval($w));
        $fingerprint['openHeight'] = rand(768, intval($h));

        return json_encode($fingerprint, JSON_UNESCAPED_UNICODE);
    }
}
